Before we go into the nitty gritty of a UI (User Interface), let’s go through where it fits into the wider picture. Please note that we will also refer to the UI as the ‘front-end’ as it’s the part of an app a user will get to see. The server is - yes, you guessed it! - the ‘backend’.
Great! Now that we know why and what a UI is, let’s learn how to create one. We’re going to start with how to use Shiny functions to lay out the different sections of an interface (i.e., a header, footer, etc.).
We have more than 1 option when determining how to layout the UI for an app (i.e., fluidPage, shinyDashboard and CSS Grid). Today we will focus mostly on using fluidpage() to create the layout of your app as it’s the most common layout function today for general Shiny apps and you will likely come across it quite often in the example code you see across the internet. This is not to say that you shouldn’t learn the other two options! Flexdahsboard() is commonly utilised in dashboard applications (i.e., your monthly MI) and the use of CSS Grid is very likely to grow in the future. In a tab above we’ve also included a slide deck by Eduardo Cortes on how to use and build an app with shinyDashboard.
As mentioned previously, fluidPage() is a layout function which defines defines the UI for your app. It will take on arguments as needed (i.e., content) that you may want your layout to contain.
#Pseudo code example of the fluidPage() layout function taking on 3 arguments
ui <- fluidPage(argument1, argument2, argument3)
#You can use an 'empty' server to test the layout of your app.
server <- function(input, output){ #empty server function
}
So I know what you’re wondering, what kind of arguments can I include and what do they do?? You can include the following argument types:
In this section, we are going to focus on the first 3 elements as they have more of a direct impact on the layout of your app.
Text elements refer to any titles, paragraphs, and headers that you may have throughout your app. We’re going to start with basic text functions and later replace them with the use of HTML/CSS. This will be especially useful if you decide to include documentation as part of your application.
The following table contains key basic Shiny text functions which you would place within the fluidpage(…) function. You can also embed certain functions within others (i.e., italicized text within a first level header).
| Shiny Text Functions | Description |
|---|---|
| p(“…”) | A paragraph of text. |
| h1(“…”) | A first level header |
| h2(“…”) | A second level header |
| h3(“…”) | A third level header |
| h4(“…”) | A fourth level header |
| br( ) | A line break (blank line) |
| strong(“…”) | Bold text |
| em(“…”) | Italicized text |
The following code uses the pseudo code we created above and includes text elements as fluidPage( ) arguments.
library(shiny)
ui <- fluidPage(
h1("First level title"), #argument 1
h2("Second level title"), #argument 2
h3("Third level title"),
h4("Fourth level title"),
h5(em("Fifth level title")), #fifth header level with italicization
br(), #line break
h6("Sixth level title") )
server <- function(input, output) { }
# Run the application
shinyApp(ui = ui, server = server)
Running the code above produces the following app.
As you can see, it’s fairly straightforward to add text in your interface. Each text function applies the same format assigned to that particular function (i.e., h1 will always produce bold, large text). This is very similar to applying the same formatting to headers in documentation which you find in Microsoft Word.
To create your own text styles, you will learn to use HTML tags later in the training. Don’t worry, we will get there!
Let’s have a little fun and practice - Using the commands you’ve just learned, see if you can create the following app:
The following table contains the various layout options that you can use. It is not an exhaustive list but does contain the functions you are most likely to need or come across. Non-fluidPage layout options are listed toward the bottom.
| Shiny UI Functions | Description |
|---|---|
| fluidPage() | All fluidPage layouts must start with this function |
| fluidRow() | Creates a row to separate content vertically |
| column() | Passed to fluidRow to separate content horizontally |
| titlePanel() | Creates a panel containing the application title. |
| headerPanel() | Creates a header panel |
| sidebarLayout() | Creates sidebar to add content |
| sidebarPanel() | The side Panel argument passed to sidebarLayout() |
| mainPanel() | The main Panel argument passed to sidebarLayout() |
| tabsetPanel() | Creates tabs on which you can add different content |
| tabPanel() | A tab passed to tabsetPanel() |
| conditionalPanel() | Creates a panel which exists when a condition is met |
| navbarPage() | Creates a page with a navigation bar at the top |
| navlistPanel() | Similar to tabPanel except that navigation is on the left instead of the top |
| wellPanel() | Creates a panel with a slightly inset border and grey background |
| inputPanel() | A flowLayout with a grey border and light grey background |
| absolutePanel() | Creates a panel whose contents are absolutely positioned |
| splitLayout() | Lays out elements horizontally, dividing the available horizontal space into equal parts (by default) |
| verticalLayout() | Create a container that includes one or more rows of content (each element passed to the container will appear on it’s own line in the UI) |
| flowLayout() | Lays out elements in a left-to-right, top-to-bottom arrangement |
You can embed certain functions inside of others, such as tabPanel( ) inside of tabsetPanel( ) or the use of sidebarPanel( ) inside of sidebarLayout( ). And yes, you could even embed sidebarLayout( ) inside of tabsetPanel( )! Just remember to separate each of these functions with a comma.
Today, we will build two fairly basic, but common layouts - the Sidebar Layout and an application using tabs.We will also quickly cover a simple example using fluidRow. Let’s start with the Sidebar Layout:
The code below produces the app. I’ve included some code that you will not understand until we go through control widgets but is there so you can view the layout.
library(shiny)
library(datasets)
data("faithful") #in build dataset
#Sidebar Layout
ui <- fluidPage( #All layout functions are held here
titlePanel("Hello 4most!"),
sidebarLayout(
sidebarPanel( #sidebarPanel feed into the sidebarLayout
# Input: Slider for the number of bins - Not expected to know this yet
sliderInput(inputid = "bins",
label = "Number of bins:",
min = 1, max = 50, value = 30) ),
mainPanel( #mainPanel to the right of the sidebarPanel
#displays output - Not expected to know this yet
plotOutput(outputID="distPlot") )
)
)
server <- function(input, output) {
# Histogram of the Old Faithful Geyser Data - Not expected to know this yet
output$distPlot <- renderPlot({
x <- faithful$waiting
bins <- seq(min(x), max(x), length.out = input$bins + 1)
hist(x, breaks = bins, col = "#75AADB", border = "white",
xlab = "Waiting time to next eruption (in mins)",
main = "Histogram of waiting times")})
}
# Run the application
shinyApp(ui = ui, server = server)
Now let’s create an application with a sidebar and content on different tabs!
As above, there are control widgets that we’ve not covered yet and are included for illustration purposes
library(shiny)
ui <- fluidPage(
br(), #included for some vertical space at the top
sidebarLayout( #holds sidebarPanel and mainPanel
sidebarPanel(
radioButtons("dist", "Distribution type:",
c("Normal" = "norm", "Uniform" = "unif",
"Log-normal" = "lnorm", "Exponential" = "exp")),
br(),
sliderInput("n", "Number of observations:",
value = 500, min = 1, max = 1000)
),
mainPanel( #Place the tabs within the main panel
tabsetPanel(type = "tabs",
tabPanel("Plot", plotOutput("plot")), #First tab and content
tabPanel("Summary", verbatimTextOutput("summary")), #Second tab
tabPanel("Table", tableOutput("table")) # Third tab and content
))
))
server <- function(input, output) {
# Reactive expression to generate the requested distribution ----
d <- reactive({
dist <- switch(input$dist,
norm = rnorm, unif = runif,
lnorm = rlnorm, exp = rexp, rnorm)
dist(input$n) })
# Generate a plot of the data - Displayed on first tab
output$plot <- renderPlot({
dist <- input$dist
n <- input$n
hist(d(), main = paste("r", dist, "(", n, ")", sep = ""),
col = "#75AADB", border = "white") })
# Generate a summary of the data - Displayed on second tab
output$summary <- renderPrint({ summary(d()) })
# Generate an HTML table view of the data - Displayed on third tab
output$table <- renderTable({ d() })
}
shinyApp(ui, server)
The next example covers the use of fluidRow( ). fluidRow can include further layout functions (such as column as illustrated below) or specific content that you want to lay within the same row. Content outside of fluidRow( ) will fall in a different vertical space. For example, the titlePanel( ) containing “Hello 4most!” is outside of fluidRow( ) and therefore the content is not on the same row as the text and 4most logo:
library(shiny)
#Sidebar Layout
ui <- fluidPage(
titlePanel("Hello 4most!"),
fluidRow( #Insert 3 columns adding up to the 12 unit width used in fluidPage()
column(3, h1("Column Width 3")), #This column (width=3)can remain empty for empty space
column(6, img(src="4Most.png", height = 100, width = 100, align ="left")),
column(3, h1("Column Width 3")) #Third column with width 3
)
)
server <- function(input, output) {
#Empty for this example }
shinyApp(ui = ui, server = server)
Remember that www/ folder we initially created when setting up your working directory? Well, now is when it comes to use!
Folder Structure
| shinyApp/
| ui.R
| server.R
| www/ #Folder in which you place all images and logos you want to embed.
| image.png
Once you’ve placed the image in the www/ folder, you will need to call it from within the layout function/area you want shiny to display it from. As an example, we’re going to add an image to the example we previously practiced with (adding text elements):
To insert the Star Wars image you see above, you will place the image in the www/ folder and decide where in the app you want to see it. In the example above, I’ve created a row below the text and inserted the image there as shown in the code:
library(shiny)
ui <- fluidPage(
h4(em("A long time ago")),
h2(em("in a galaxy")),
h1(em("far, far away...")),
fluidRow(img(src="starwars3.jpg")))
server <- function(input, output) { }
# Run the application
shinyApp(ui = ui, server = server)
This is not your only option. You can also insert images using HTML. We will go further into HTML when we’re further along in the training but the following example code demonstrates how we could utilise HTML instead to produce the same app. I’ve also added a command to center the image in this example:
library(shiny)
ui <- fluidPage(
h4(em("A long time ago")),
h2(em("in a galaxy")),
h1(em("far, far away...")),
HTML('<center><img src="starwars3.jpg"></center>'))
server <- function(input, output) { }
# Run the application
shinyApp(ui = ui, server = server)
Time to think about your app!
Are there any images or logos you would like to include? In which section of your app are planning to place them in?
Below we’ve embedded a Shiny app built by our very own Eduardo Cortes which illustrates the use of shinyDashboard - an alternative to using fluidPage( ). Depending on the motivation behind your app, using shinyDashboard may make more sense. Right click to open separately
Let’s walk through the creation of the app with Eduardo (Download here):
Practice your knowledge! Try this before you look at the solutions - I guarantee you will find it harder when you’re building an app if you don’t.
For solutions - Click on this link (Solutions are embedded shiny apps so you may need to reload any that time out).
UI Design Considerations
The design of how our clients will interact with our output is an issue we don’t often come across and so we’ve included some useful items to consider when designing the layout and general appearance of your Shiny application.
If you were to find yourself lost in London today, you would immediately look for street signs, tube/bus stations or other recognizable landmarks. Similarly, users are accustomed to finding certain signposts on websites in certain areas (i.e., link to home page at the top). Unless you’re asked for something specific from a client, you can you use these expectations as a blueprint for your app.
The image above illustrates where users typically go when using a website based on various eye-tracking studies. When looking for the home page, users tend to focus to the top and and to the right. When looking for navigation links, users look to the left or towards the top. Essentially if users have these expectations, make it easy for you and your end-user and don’t reinvent the wheel!
This is essentially the concept of aligning the appearance of your content to how it important it is. This is something we naturally do when writing model documentation - Main Headers are typically the largest, boldest text and sub-headers are in smaller text.
Although the example above uses text, visual hierarchy applies to all content. Try not to display graphs/controls of less importance in the same manner as the more key graphs/controls. The difference will guide the user towards the key components of the app.
Clarity, in this context, is the idea that your end-user should know what to do at all times and not be left wondering how to work through your app. Keep in mind that lack of clarity can stem from both too little or too much information so take a step back and see where you can clean up or add content.
Users expect interactive elements to be placed near the object they wish to control (i.e., submit button right below a form rather than set at the top of a page).If you want your user to be able to manipulate a certain graph, ensure that the options, such as a drop-down list, are near to the graph they will be changing.
Ensure interface elements (i.e., buttons) have a clear meaning if you decide to use graphic elements. For instance, using a house graphic for the home page if fairly clear rather than using a box.
Defaults - most people never reset default settings. We still see plenty of people using the default display in SAS despite display settings that are easier on the eyes. With this in mind, ensure that any default settings you include (i.e., sample sizes, where sliders start and end, etc.) are useful and make sense in the context you’re using them in.
You should ensure that graphics, terminology and formatting are consistent throughout the app and not simply consistent in one sectio of the app. An icon should represent the same thing throughout the app and not multiple different concepts that are dependent on context (i.e., don’t use a home icon to mean home page and go to the top of a section - one or the other, not both). Dates should be in the same format from one tab to another. Color palette choice, unless for a specific reason, should also be consistent to ensure a professional appearance.
Your end user should always know what is going on - If you press a button, how do you know if something is actually happening or if the action is broken? Always supply a visible way of displaying this. For instance, a rolling button or a message(i.e., “Model processing”).
Time for the next step in creating our app today:
The following website is a free resources that allows you to draft the layout to a website/application. Click on me to open